﻿@using System.Linq.Expressions
@using System.Linq.Dynamic.Core
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Forms
@using Radzen;
@using Radzen.Blazor.Rendering
@typeparam TItem
@inherits PagedDataBoundComponent<TItem>
@if (Columns != null)
{
    <CascadingValue Value=this>
        @Columns
    </CascadingValue>
}
@if (Visible)
{
    var grid = Reference;
    var visibleColumns = columns.Where(c => c.Visible).ToList();
<div @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
    @if (AllowPaging && (PagerPosition == PagerPosition.Top || PagerPosition == PagerPosition.TopAndBottom))
    {
            <RadzenPager @ref="topPager" Count="@Count" PageSize="@PageSize" PageNumbersCount="@PageNumbersCount" PageChanged="@OnPageChanged" PageSizeChanged="@OnPageSizeChanged" PageSizeOptions="@PageSizeOptions" ShowPagingSummary="@ShowPagingSummary" Density="@Density" />
    }
    <div class="rz-datatable-scrollable-wrapper rz-helper-clearfix" style="">
        <div class="rz-datatable-scrollable-view">

            <div class="rz-datatable-scrollable-header">
                <div @ref="@scrollableHeader" class="rz-datatable-scrollable-header-box" style="@getHeaderStyle()">
                    <table>
                        <colgroup class="rz-datatable-scrollable-colgroup">
                            @if (Template != null)
                            {
                                <col>
                            }
                            @foreach (var column in visibleColumns)
                            {
                                <col id=@(getColumnUniqueId(visibleColumns.IndexOf(column)) + "-col") style="@column.GetStyle()">
                            }
                        </colgroup>
                        <thead class="rz-datatable-thead">
                            <tr class="">
                                @if (Template != null)
                                {
                                    <th class="rz-col-icon  rz-unselectable-text" scope="col">
                                        <span class="rz-column-title"></span>
                                    </th>
                                }

                                @foreach (var column in visibleColumns)
                                {
                                    var sortableClass = AllowSorting && column.Sortable ? "rz-sortable-column" : "";
                                    <th class="@($" rz-unselectable-text {sortableClass} {column.HeaderCssClass}")" scope="col" style="@column.GetStyle(true)">
                                        <div @onclick='@((args) => OnSort(args, column))' style="width:100%">
                                            <span class="rz-column-title">
                                                @if (column.HeaderTemplate != null)
                                                {
                                                    @column.HeaderTemplate
                                                }
                                                else
                                                {
                                                    @column.Title
                                                }
                                            </span>
                                            @if (AllowSorting && column.Sortable)
                                            {
                                                @if (column.SortOrder == SortOrder.Ascending)
                                                {
                                                    <span class="rz-sortable-column-icon rzi-grid-sort rzi-sort rzi-sort-asc"></span>
                                                }
                                                else if (column.SortOrder == SortOrder.Descending)
                                                {
                                                    <span class="rz-sortable-column-icon rzi-grid-sort rzi-sort rzi-sort-desc"></span>
                                                }
                                                else
                                                {
                                                    <span class="rz-sortable-column-icon rzi-grid-sort rzi-sort"></span>
                                                }
                                            }
                                            @if (AllowColumnResize)
                                            {
                                                var columnIndex = visibleColumns.IndexOf(column);
                                                <div id="@(getColumnUniqueId(columnIndex) + "-resizer")" style="cursor:col-resize;float:right;"
                                                     @onclick:preventDefault="true" @onclick:stopPropagation="true"
                                                     @onmousedown=@(args => StartColumnResize(args, columnIndex))>&nbsp;</div>
                                            }
                                            @if (AllowFiltering && column.Filterable && FilterMode == FilterMode.Advanced)
                                            {
                                                <i @onclick:stopPropagation="true" onclick="@($"Radzen.togglePopup(this, '{PopupID}{column.GetFilterProperty()}')")"
                                                   class="@getFilterIconCss(column)" />

                                                <div id="@($"{PopupID}{column.GetFilterProperty()}")" class="rz-overlaypanel"
                                                     style="display:none;min-width:250px;" tabindex="0">
                                                    <div class="rz-grid-filter rz-overlaypanel-content">
                                                        @if (column.FilterTemplate != null)
                                                        {
                                                            @column.FilterTemplate(column)
                                                        }
                                                        else
                                                        {
                                                            <RadzenLabel Text="@FilterText" class="rz-grid-filter-label" />
                                                            <RadzenDropDown @onclick:preventDefault="true" Data="@(column.FilterOperators)" TextProperty="Value" ValueProperty="Key" TValue="string" Value="@column.FilterOperator" Change="@(args => column.SetFilterOperator($"{args}"))" />
                                                            @if (column.Type == "number")
                                                            {
                                                                @(DrawNumericFilter(column, false))
                                                            }
                                                            else if (column.Type == "string" && (column.Format == "date-time" || column.Format == "date-time-offset"))
                                                            {
                                                                <RadzenDatePicker TValue="@object" ShowTime="true" ShowTimeOkButton="true" DateFormat="@getFilterDateFormat(column)"
                                                                                  Value="@column.FilterValue" Change="@(args => column.SetFilterValue(args.Value))" />

                                                            }
                                                            else if (column.Type == "boolean")
                                                            {
                                                                <RadzenCheckBox TriState="true" TValue="@object" Value="@column.FilterValue" Change="@((args) => OnFilter(new ChangeEventArgs() { Value = args }, column))" />
                                                            }
                                                            else
                                                            {
                                                                <RadzenTextBox Value="@($"{column.FilterValue}")" Change="@(args => column.SetFilterValue(args))" />
                                                            }

                                                            <RadzenDropDown @onclick:preventDefault="true" TextProperty="Text" ValueProperty="Value" Style="width: 90px"
                                                                            Data="@(Enum.GetValues(typeof(LogicalFilterOperator)).Cast<LogicalFilterOperator>().Select(t => new { Text = t == LogicalFilterOperator.And ? AndOperatorText : OrOperatorText, Value = t }))" TValue="LogicalFilterOperator" Value="@column.LogicalFilterOperator" Change="@(args => column.SetLogicalFilterOperator((LogicalFilterOperator)args))" />

                                                            <RadzenDropDown @onclick:preventDefault="true" Data="@(column.FilterOperators)" TextProperty="Value" ValueProperty="Key" TValue="string" Value="@column.SecondFilterOperator" Change="@(args => column.SetSecondFilterOperator($"{args}"))" />
                                                            @if (column.Type == "number")
                                                            {
                                                                @(DrawNumericFilter(column, false, false))
                                                            }
                                                            else if (column.Type == "string" && (column.Format == "date-time" || column.Format == "date-time-offset"))
                                                            {
                                                                <RadzenDatePicker TValue="@object"
                                                                                  ShowTime="true" ShowTimeOkButton="true" DateFormat="@getFilterDateFormat(column)"
                                                                                  Value="@column.SecondFilterValue" Change="@(args => column.SetFilterValue(args.Value, false))" />

                                                            }
                                                            else if (column.Type == "boolean")
                                                            {
                                                                <RadzenCheckBox TriState="true" TValue="@object" Value="@column.SecondFilterValue" Change="@((args) => OnFilter(new ChangeEventArgs() { Value = args }, column, false))" />
                                                            }
                                                            else
                                                            {
                                                                <RadzenTextBox Value="@($"{column.SecondFilterValue}")" Change="@(args => column.SetFilterValue(args, false))" />
                                                            }
                                                        }
                                                    </div>
                                                    @if (column.FilterTemplate == null)
                                                    {
                                                        <div class="rz-grid-filter-buttons">
                                                            <RadzenButton ButtonStyle="ButtonStyle.Secondary" Text=@ClearFilterText Click="@((args) => ClearFilter(column, true))" />
                                                            <RadzenButton ButtonStyle="ButtonStyle.Primary" Text=@ApplyFilterText Click="@((args) => ApplyFilter(column, true))" />
                                                        </div>
                                                    }
                                                </div>
                                            }
                                        </div>
                                    </th>
                                }
                            </tr>
                            @if (AllowFiltering && FilterMode == FilterMode.Simple && columns.Where(column => column.Filterable && (!string.IsNullOrEmpty(column.GetFilterProperty()) || column.FilterTemplate != null)).Any())
                            {
                                <tr>
                                    @if (Template != null)
                                    {
                                        <th class="rz-col-icon  rz-unselectable-text" scope="col">
                                            <span class="rz-column-title"></span>
                                        </th>
                                    }
                                    @foreach (var column in visibleColumns)
                                    {
                                        <th class="@($" rz-unselectable-text")" scope="col" style="@column.GetStyle(true)">
                                            @if (AllowFiltering && column.Filterable && (!string.IsNullOrEmpty(column.GetFilterProperty()) || column.FilterTemplate != null))
                                            {
                                                <div class="rz-cell-filter">
                                                    <div class="rz-cell-filter-content">
                                                        @if (column.FilterTemplate != null)
                                                        {
                                                            @column.FilterTemplate(column)
                                                        }
                                                        else
                                                        {
                                                            <label class="rz-cell-filter-label" style="height:35px">
                                                                @if (column.Type == "string" && column.Format == "date-time")
                                                                {
                                                                    <button class="rz-button rz-button-md rz-button-icon-only rz-variant-flat rz-light" onclick="@($"Radzen.togglePopup(this.parentNode, '{PopupID}{column.GetFilterProperty()}')")">
                                                                        <i class="rzi">date_range</i>
                                                                    </button>
                                                                    @if (column.FilterValue != null)
                                                                    {
                                                                        <span class="rz-current-filter">@column.FilterValue</span>
                                                                        <i @onclick="@((args) => ClearFilter(column))" class="rzi rz-cell-filter-clear">close</i>
                                                                    }
                                                                    <div id="@($"{PopupID}{column.GetFilterProperty()}")" class="rz-overlaypanel"
                                                                         style="display:none;width:550px;" tabindex="0">
                                                                        <div class="rz-overlaypanel-content">

                                                                            <div class="rz-date-filter">

                                                                                <div class="rz-listbox rz-inputtext   ">
                                                                                    <div class="rz-listbox-list-wrapper">
                                                                                        <ul class="rz-listbox-list">
                                                                                            <li class="@(DateFilterOperatorStyle(column, "eq"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "eq"))" style="display: block;">
                                                                                                <span>@EqualsText</span>
                                                                                            </li>
                                                                                            <li class="@(DateFilterOperatorStyle(column, "ne"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "ne"))" style="display: block;">
                                                                                                <span>@NotEqualsText</span>
                                                                                            </li>
                                                                                            <li class="@(DateFilterOperatorStyle(column, "lt"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "lt"))" style="display: block;">
                                                                                                <span>@LessThanText</span>
                                                                                            </li>
                                                                                            <li class="@(DateFilterOperatorStyle(column, "le"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "le"))" style="display: block;">
                                                                                                <span>@LessThanOrEqualsText</span>
                                                                                            </li>
                                                                                            <li class="@(DateFilterOperatorStyle(column, "gt"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "gt"))" style="display: block;">
                                                                                                <span>@GreaterThanText</span>
                                                                                            </li>
                                                                                            <li class="@(DateFilterOperatorStyle(column, "ge"))" @onclick="@((args) => ApplyDateFilterByFilterOperator(column, "ge"))" style="display: block;">
                                                                                                <span>@GreaterThanOrEqualsText</span>
                                                                                            </li>
                                                                                        </ul>
                                                                                    </div>
                                                                                </div>

                                                                                <RadzenDatePicker TValue="@object"
                                                                                                  ShowTime="true" ShowTimeOkButton="false" Inline="true" DateFormat="@getFilterDateFormat(column)"
                                                                                                  Value="@column.FilterValue" Change="@(args => column.SetFilterValue(args.Value))" />

                                                                            </div>
                                                                            <div class="rz-date-filter-buttons">
                                                                                <button class="rz-button rz-clear-filter" @onclick="@((args) => ClearFilter(column, true))">
                                                                                    @ClearFilterText
                                                                                </button>
                                                                                <button class="rz-button rz-apply-filter" @onclick="@((args) => ApplyFilter(column, true))">
                                                                                    @ApplyFilterText
                                                                                </button>
                                                                            </div>

                                                                        </div>
                                                                    </div>
                                                                }
                                                                else if (column.Type == "number")
                                                                {
                                                                    @(DrawNumericFilter(column))
                                                                }
                                                                else if (column.Type == "boolean")
                                                                {
                                                                    <RadzenCheckBox TriState="true" TValue="@object" Value="@column.FilterValue" Change="@((args) => OnFilter(new ChangeEventArgs() { Value = args }, column))" />
                                                                }
                                                                else
                                                                {
                                                                    <input id="@(getFilterInputId(column))" @onchange="@((args) => OnFilter(args, column))" @onkeydown="@((args) => OnFilterKeyPress(args, column))" value="@column.FilterValue" type="text" class="rz-textbox" style="width: 100%;" />
                                                                }
                                                            </label>
                                                        }
                                                    </div>
                                                </div>
                                            }
                                        </th>
                                    }
                                </tr>
                            }
                        </thead>
                    </table>
                </div>
            </div>

            <div @ref="@scrollableBody" class="rz-datatable-scrollable-body" onscroll="Radzen.scrollDataGrid(event)">
                <div class="rz-datatable-scrollable-table-wrapper" style="position:relative">
                    <table style="top:0px">
                        <colgroup class="rz-datatable-scrollable-colgroup">
                            @if (Template != null)
                            {
                                <col>
                            }
                            @foreach (var column in visibleColumns)
                            {
                                <col id=@(getColumnUniqueId(visibleColumns.IndexOf(column)) + "-data-col") style="@column.GetStyle()">
                            }
                        </colgroup>
                        <tbody class="rz-datatable-data  rz-datatable-hoverable-rows">
                            @if (Data != null)
                            {
                                @if (Count > 0)
                                {
                                    int i = 0;
                                    @foreach (var item in LoadData.HasDelegate ? Data : PagedView)
                                    {
                                        var rowArgs = RowAttributes(item, i);

                                        <RadzenGridRow CssClass="@(RowStyle(item, i))" Attributes="@(rowArgs.Item2)" InEditMode="@IsRowInEditMode(item)">
                                            @if (Template != null)
                                            {
                                        <td class="rz-col-icon">
                                            <span class="rz-column-title"></span>
                                            @if (rowArgs.Item1.Expandable)
                                            {
                                                <a href="javascript:void(0)" @onclick="@(_ => ExpandItem(item))">
                                                    <span class="@(ExpandedItemStyle(item))"></span>
                                                </a>
                                            }
                                        </td>
                                    }

                                            @for (var j = 0; j < visibleColumns.Count; j++)
                                            {
                                                if (rowSpans.ContainsKey(j))
                                                {
                                                    rowSpans[j] = rowSpans[j] - 1;

                                                    if (rowSpans[j] <= 0)
                                                    {
                                                        rowSpans.Remove(j);
                                                    }
                                                    else
                                                    {
                                                        continue;
                                                    }
                                                }

                                                var column = visibleColumns[j];
                                                var cellAttr = CellAttributes(item, column);

                                                object colspan;
                                                cellAttr.TryGetValue("colspan", out colspan);

                                                if (colspan != null)
                                                {
                                                    j = j + (int)Convert.ChangeType(colspan, TypeCode.Int32) - 1;
                                                }

                                                object rowspan;
                                                cellAttr.TryGetValue("rowspan", out rowspan);

                                                if (rowspan != null)
                                                {
                                                    rowSpans.Add(j, (int)Convert.ChangeType(rowspan, TypeCode.Int32));
                                                }

                                                if (IsRowInEditMode(item))
                                                {
                                                    <CascadingValue Value=editContexts[item]>
                                                        <td style="@column.GetStyle(true)" @attributes="@(cellAttr)">
                                                            <span class="rz-cell-data">
                                                                @if (column.EditTemplate != null)
                                                                {
                                                                    @column.EditTemplate(item)
                                                                }
                                                                else if (column.Template != null)
                                                                {
                                                                    @column.Template(item)
                                                                }
                                                                else if (!string.IsNullOrEmpty(column.Property))
                                                                {
                                                                    @if (!string.IsNullOrEmpty(column.FormatString))
                                                                    {
                                                                        @(String.Format(column.FormatString, PropertyAccess.GetValue(item, column.Property)));
                                                                    }
                                                                    else if (!string.IsNullOrEmpty(column.Property))
                                                                    {
                                                                        @(PropertyAccess.GetValue(item, column.Property));
                                                                    }
                                                                }
                                                            </span>
                                                        </td>
                                                    </CascadingValue>
                                                }
                                                else
                                                {
                                                    <RadzenGridCell Grid="@this" Item="@item"
                                                        Style="@column.GetStyle(true)" CssClass="@column.CssClass" Attributes="@(cellAttr)">
                                                        @if (Responsive)
                                                        {
                                                            <span class="rz-column-title">
                                                                @if (column.HeaderTemplate != null)
                                                                {
                                                                    @column.HeaderTemplate
                                                                }
                                                                else
                                                                {
                                                                    @column.Title
                                                                }
                                                            </span>
                                                        }
                                                        <span class="rz-cell-data">
                                                            @if (item != null)
                                                            {
                                                                @if (column.Template != null)
                                                                {
                                                                    @column.Template(item)
                                                                }
                                                                else if (!string.IsNullOrEmpty(column.Property))
                                                                {
                                                                    @if (!string.IsNullOrEmpty(column.FormatString))
                                                                    {
                                                                        @(String.Format(column.FormatString, PropertyAccess.GetValue(item, column.Property)));
                                                                    }
                                                                    else if (!string.IsNullOrEmpty(column.Property))
                                                                    {
                                                                        @(PropertyAccess.GetValue(item, column.Property));
                                                                    }
                                                                }
                                                            }
                                                        </span>
                                                    </RadzenGridCell>
                                                }
                                            }
                                            </RadzenGridRow>
                                            @if (Template != null && expandedItems.Keys.Contains(item))
                                            {
                                                <tr class="rz-expanded-row-content">
                                                    <td colspan="@(visibleColumns.Count + 1)">
                                                        <div class="rz-expanded-row-template">
                                                            @Template(item)
                                                        </div>
                                                    </td>
                                                </tr>
                                            }
                                            i++;
                                        }
                                }
                                else
                                {
                                    <tr class=" rz-datatable-emptymessage-row">
                                        <td class="rz-datatable-emptymessage" colspan="@visibleColumns.Count">
                                            @if (EmptyTemplate != null)
                                            {
                                                @EmptyTemplate
                                            }
                                            else
                                            {
                                                <span>@EmptyText</span>
                                            }
                                        </td>
                                    </tr>
                                }
                            }
                            </tbody>
                        </table>
                    </div>
                </div>
                @if (columns.Where(c => c.Visible && c.FooterTemplate != null).Any())
                {
                    <div class="rz-datatable-scrollable-footer">
                        <div class="rz-datatable-scrollable-footer-box" style="margin-right: 0px;">
                            <table>
                                <colgroup class="rz-datatable-scrollable-colgroup">
                                    @if (Template != null)
                                    {
                                        <col>
                                    }
                                    @foreach (var column in visibleColumns)
                                    {
                                        <col id=@(getColumnUniqueId(visibleColumns.IndexOf(column)) + "-footer-col") style="@column.GetStyle()">
                                    }
                                </colgroup>
                                <tfoot class="rz-datatable-tfoot">
                                    <tr class="">
                                        @if (Template != null)
                                        {
                                            <td class="rz-col-icon  rz-unselectable-text" scope="col">
                                                <span class="rz-column-title"></span>
                                            </td>
                                        }
                                        @foreach (var column in visibleColumns)
                                        {
                                            <td class="@($" {column.FooterCssClass}")" style="@column.GetStyle(true)">
                                                <span class="rz-column-footer">
                                                    @if (column.FooterTemplate != null)
                                                    {
                                                        @column.FooterTemplate
                                                    }
                                                </span>
                                            </td>
                                        }
                                    </tr>
                                </tfoot>
                            </table>
                        </div>
                    </div>
                }
            </div>
        </div>

        @if (AllowPaging && (PagerPosition == PagerPosition.Bottom || PagerPosition == PagerPosition.TopAndBottom))
        {
            <RadzenPager @ref="bottomPager" Count="@Count" PageSize="@PageSize" PageNumbersCount="@PageNumbersCount" PageChanged="@OnPageChanged" PageSizeChanged="@OnPageSizeChanged" PageSizeOptions="@PageSizeOptions" ShowPagingSummary="@ShowPagingSummary" class="rz-paginator-bottom" Density="@Density" />
        }
    </div>
    }
@code {
    Dictionary<int, int> rowSpans = new Dictionary<int, int>();

    ElementReference scrollableHeader;
    ElementReference scrollableBody;

    string getFilterDateFormat(RadzenGridColumn<TItem> column)
    {
        if (column != null && !string.IsNullOrEmpty(column.FormatString))
        {
            return column.FormatString.Replace("{0:", "").Replace("}", "");
        }

        return FilterDateFormat;
    }

    [Parameter]
    public LogicalFilterOperator LogicalFilterOperator { get; set; } = LogicalFilterOperator.And;

    [Parameter]
    public FilterMode FilterMode { get; set; } = FilterMode.Simple;

    [Parameter]
    public DataGridExpandMode ExpandMode { get; set; } = DataGridExpandMode.Multiple;

    [Parameter]
    public DataGridEditMode EditMode { get; set; } = DataGridEditMode.Multiple;

    [Parameter]
    public string FilterText { get; set; } = "Filter";

    [Parameter]
    public string AndOperatorText { get; set; } = "And";

    [Parameter]
    public string OrOperatorText { get; set; } = "Or";

    [Parameter]
    public string ApplyFilterText { get; set; } = "Apply";

    [Parameter]
    public string ClearFilterText { get; set; } = "Clear";

    [Parameter]
    public string EqualsText { get; set; } = "Equals";

    [Parameter]
    public string NotEqualsText { get; set; } = "Not equals";

    [Parameter]
    public string LessThanText { get; set; } = "Less than";

    [Parameter]
    public string LessThanOrEqualsText { get; set; } = "Less than or equals";

    [Parameter]
    public string GreaterThanText { get; set; } = "Greater than";

    [Parameter]
    public string GreaterThanOrEqualsText { get; set; } = "Greater than or equals";

    [Parameter]
    public string EndsWithText { get; set; } = "Ends with";

    [Parameter]
    public string ContainsText { get; set; } = "Contains";

    [Parameter]
    public string StartsWithText { get; set; } = "Starts with";

    class NumericFilterEventCallback
    {
        public static EventCallback<T> Create<T>(object receiver, Action<T> action)
        {
            return EventCallback.Factory.Create<T>(receiver, action);
        }

        public static Action<T> Action<T>(Action<object> action)
        {
            return args => action(args);
        }
    }

    RenderFragment DrawNumericFilter(RadzenGridColumn<TItem> column, bool force = true, bool isFirst = true)
    {
        return new RenderFragment(builder =>
        {
            var type = Nullable.GetUnderlyingType(column.FilterPropertyType) != null ?
                column.FilterPropertyType : typeof(Nullable<>).MakeGenericType(column.FilterPropertyType);

            var numericType = typeof(RadzenNumeric<>).MakeGenericType(type);

            builder.OpenComponent(0, numericType);

            builder.AddAttribute(1, "Value", isFirst ? column.FilterValue : column.SecondFilterValue);
            builder.AddAttribute(2, "Style", "width:100%");

            Action<object> action;
            if (force)
            {
                action = args => OnFilter(new ChangeEventArgs() { Value = args }, column, isFirst);
            }
            else
            {
                action = args => column.SetFilterValue(args, isFirst);
            }

            var eventCallbackGenericCreate = typeof(NumericFilterEventCallback).GetMethod("Create").MakeGenericMethod(type);
            var eventCallbackGenericAction = typeof(NumericFilterEventCallback).GetMethod("Action").MakeGenericMethod(type);

            builder.AddAttribute(3, "Change", eventCallbackGenericCreate.Invoke(this,
                new object[] { this, eventCallbackGenericAction.Invoke(this, new object[] { action }) }));

            builder.CloseComponent();
        });
    }

    string getFilterInputId(RadzenGridColumn<TItem> column)
    {
        return string.Join("", $"{UniqueID}".Split('.')) + column.GetFilterProperty();
    }

    [Parameter]
    public FilterCaseSensitivity FilterCaseSensitivity { get; set; } = FilterCaseSensitivity.Default;

    [Parameter]
    public int FilterDelay { get; set; } = 500;

    [Parameter]
    public string FilterDateFormat { get; set; }

    [Parameter]
    public string ColumnWidth { get; set; }

    private string _emptyText = "No records to display.";
    [Parameter]
    public string EmptyText
    {
        get { return _emptyText; }
        set
        {
            if (value != _emptyText)
            {
                _emptyText = value;
            }
        }
    }

    [Parameter]
    public RenderFragment EmptyTemplate { get; set; }

    [Parameter]
    public bool AllowSorting { get; set; }

    [Parameter]
    public bool AllowFiltering { get; set; }

    [Parameter]
    public bool AllowColumnResize { get; set; }

    string getColumnUniqueId(int columnIndex)
    {
        return string.Join("", $"{UniqueID}".Split('.')) + columnIndex;
    }

    async Task StartColumnResize(MouseEventArgs args, int columnIndex)
    {
        await JSRuntime.InvokeVoidAsync("Radzen.startColumnResize", getColumnUniqueId(columnIndex), Reference, columnIndex, args.ClientX);
    }

    [JSInvokable("RadzenGrid.OnColumnResized")]
    public async Task OnColumnResized(int columnIndex, double value)
    {
        var column = this.ColumnsCollection.Where(c => c.Visible).ToList()[columnIndex];
        column.SetWidth($"{value}px");
        await ColumnResized.InvokeAsync(new ColumnResizedEventArgs<TItem>
        {
            Column = column,
            Width = value,
        });
    }

    [Parameter]
    public EventCallback<ColumnResizedEventArgs<TItem>> ColumnResized { get; set; }

    public override IQueryable<TItem> View
    {
        get
        {
            var view = base.View.Where<TItem>(columns);

            if (!string.IsNullOrEmpty(orderBy))
            {
                if (typeof(TItem) == typeof(object))
                {
                    var firstItem = view.FirstOrDefault();
                    if (firstItem != null)
                    {
                        view = view.Cast(firstItem.GetType()).AsQueryable().OrderBy(orderBy).Cast<TItem>();
                    }
                }
                else
                {
                    view = view.OrderBy(orderBy);
                }
            }

            return view;
        }
    }


    object _value;

    [Parameter]
    public object Value
    {
        get
        {
            return _value;
        }
        set
        {
            if (!object.Equals(_value, value))
            {
                _value = value;

                selectedItems.Clear();

                if (_value != null)
                {
                    var values = _value as IEnumerable<TItem>;
                    if (values != null && SelectionMode == DataGridSelectionMode.Multiple)
                    {
                        values.ToList().ForEach(i => selectedItems.Add(i, true));
                    }
                    else
                    {
                        selectedItems.Add((TItem)_value, true);
                    }
                }

                ValueChanged?.Invoke(_value);
            }
        }
    }

    [Parameter]
    public Action<object> ValueChanged { get; set; }

    [Parameter]
    public EventCallback<TItem> RowSelect { get; set; }

    [Parameter]
    public EventCallback<TItem> RowDeselect { get; set; }

    [Parameter]
    public EventCallback<TItem> RowClick { get; set; }

    [Parameter]
    public EventCallback<TItem> RowDoubleClick { get; set; }

    [Parameter]
    public EventCallback<TItem> RowExpand { get; set; }

    [Parameter]
    public EventCallback<TItem> RowCollapse { get; set; }

    [Parameter]
    public Action<RowRenderEventArgs<TItem>> RowRender { get; set; }

    [Parameter]
    public Action<CellRenderEventArgs<TItem>> CellRender { get; set; }

    [Parameter]
    public Action<GridRenderEventArgs<TItem>> Render { get; set; }

    [Parameter]
    public RenderFragment Columns { get; set; }

    public IList<RadzenGridColumn<TItem>> ColumnsCollection
    {
        get
        {
            return columns;
        }
    }

    [Parameter]
    public bool Responsive { get; set; } = true;

    protected override void OnDataChanged()
    {
        Reset(!IsOData() && !LoadData.HasDelegate);
    }

    public void Reset(bool resetColumnFilters = true, bool resetRowState = false)
    {
        _view = null;
        _value = default(TItem);

        if (resetRowState)
        {
            selectedItems.Clear();
            expandedItems.Clear();
        }

        if (resetColumnFilters)
        {
            columns.ForEach(c => { c.SetFilterValue(null); c.SetSecondFilterOperator(null); });
        }
    }

    public async override Task Reload()
    {
        _view = null;

        if (Data != null && !LoadData.HasDelegate)
        {
            Count = View.Count();
        }

        Query.Skip = skip;
        Query.Top = PageSize;
        Query.OrderBy = orderBy;

        var filterString = columns.ToFilterString<TItem>();
        Query.Filter = filterString;

        if (LoadData.HasDelegate)
        {
            await LoadData.InvokeAsync(new Radzen.LoadDataArgs()
            {
                Skip = skip,
                Top = PageSize,
                OrderBy = orderBy,
                Filter = IsOData() ? columns.ToODataFilterString<TItem>() : filterString,
                Filters = columns.Where(c => c.Filterable && c.Visible && c.FilterValue != null).Select(c => new FilterDescriptor()
                {
                    Property = c.GetFilterProperty(),
                    FilterValue = c.FilterValue,
                    FilterOperator = (FilterOperator)Enum.Parse(typeof(FilterOperator), QueryableExtension.FilterOperators[c.FilterOperator]),
                    SecondFilterValue = c.SecondFilterValue,
                    SecondFilterOperator = (FilterOperator)Enum.Parse(typeof(FilterOperator), QueryableExtension.FilterOperators[c.SecondFilterOperator]),
                    LogicalFilterOperator = c.LogicalFilterOperator
                }),
                Sorts = columns.Where(c => c.Sortable && c.Visible && (orderBy.Contains($"{c.GetSortProperty()} asc") || orderBy.Contains($"{c.GetSortProperty()} desc"))).Select(c => new SortDescriptor()
                {
                    Property = c.GetSortProperty(),
                    SortOrder = orderBy.Contains($"{c.GetSortProperty()} asc") ? SortOrder.Ascending : SortOrder.Descending,
                })
            });
        }

        CalculatePager();

        if (!LoadData.HasDelegate)
        {
            StateHasChanged();
        }
    }

    internal async Task ChangeState()
    {
        await InvokeAsync(StateHasChanged);
    }

    protected override Task OnParametersSetAsync()
    {
        if (Visible && !LoadData.HasDelegate && _view == null)
        {
            InvokeAsync(Reload);
        }
        else
        {
            CalculatePager();
        }

        return Task.CompletedTask;
    }

    Dictionary<TItem, bool> expandedItems = new Dictionary<TItem, bool>();
    protected string ExpandedItemStyle(TItem item)
    {
        return expandedItems.Keys.Contains(item) ? "rz-row-toggler rzi-grid-sort  rzi-chevron-circle-down" : "rz-row-toggler rzi-grid-sort  rzi-chevron-circle-right";
    }

    Dictionary<TItem, bool> selectedItems = new Dictionary<TItem, bool>();
    protected string RowStyle(TItem item, int index)
    {
        var evenOrOdd = index % 2 == 0 ? "rz-datatable-even" : "rz-datatable-odd";

        return (RowSelect.HasDelegate || ValueChanged != null || SelectionMode == DataGridSelectionMode.Multiple) && selectedItems.Keys.Contains(item) ? $"rz-state-highlight {evenOrOdd} " : $"{evenOrOdd} ";
    }

    protected Tuple<Radzen.RowRenderEventArgs<TItem>, IReadOnlyDictionary<string, object>> RowAttributes(TItem item, int index)
    {
        var args = new Radzen.RowRenderEventArgs<TItem>() { Data = item, Expandable = Template != null };

        if (RowRender != null)
        {
            RowRender(args);
        }

        return new Tuple<Radzen.RowRenderEventArgs<TItem>, IReadOnlyDictionary<string, object>>(args, new System.Collections.ObjectModel.ReadOnlyDictionary<string, object>(args.Attributes));
    }

    protected IReadOnlyDictionary<string, object> CellAttributes(TItem item, RadzenGridColumn<TItem> column)
    {
        var args = new Radzen.CellRenderEventArgs<TItem>() { Data = item, Column = column };

        if (CellRender != null)
        {
            CellRender(args);
        }

        return new System.Collections.ObjectModel.ReadOnlyDictionary<string, object>(args.Attributes);
    }

    private bool visibleChanged = false;
    private bool firstRender = true;

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var emptyTextChanged = parameters.DidParameterChange(nameof(EmptyText), EmptyText);
        if (emptyTextChanged)
        {
            await ChangeState();
        }

        visibleChanged = parameters.DidParameterChange(nameof(Visible), Visible);

        await base.SetParametersAsync(parameters);

        if (visibleChanged && !firstRender)
        {
            if (Visible == false)
            {
                Dispose();
            }
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (Visible)
        {
            var args = new Radzen.GridRenderEventArgs<TItem>() { Grid = this, FirstRender = firstRender };

            if (Render != null)
            {
                Render(args);
            }
        }

        this.firstRender = firstRender;

        if (firstRender || visibleChanged)
        {
            visibleChanged = false;

            if (Visible)
            {
                await JSRuntime.InvokeVoidAsync("Radzen.adjustDataGridHeader", scrollableHeader, scrollableBody);
            }
        }
    }

    public async System.Threading.Tasks.Task ExpandRow(TItem item)
    {
        await ExpandItem(item);
    }

    protected async System.Threading.Tasks.Task ExpandItem(TItem item)
    {
        if (ExpandMode == DataGridExpandMode.Single && expandedItems.Keys.Any())
        {
            var itemToCollapse = expandedItems.Keys.FirstOrDefault();
            if (itemToCollapse != null)
            {
                expandedItems.Remove(itemToCollapse);
                await RowCollapse.InvokeAsync(itemToCollapse);

                if(object.Equals(item,itemToCollapse))
                {
                    return;
                }

            }
        }

        if (!expandedItems.Keys.Contains(item))
        {
            expandedItems.Add(item, true);
            await RowExpand.InvokeAsync(item);
        }
        else
        {
            expandedItems.Remove(item);
            await RowCollapse.InvokeAsync(item);
        }
    }

    protected string DateFilterOperatorStyle(RadzenGridColumn<TItem> column, string value)
    {
        return column.FilterOperator == value ?
            "rz-listbox-item  rz-state-highlight" :
            "rz-listbox-item ";
    }

    protected async Task ClearFilter(RadzenGridColumn<TItem> column, bool closePopup = false)
    {
        if (closePopup)
        {
            await JSRuntime.InvokeVoidAsync("Radzen.closePopup", $"{PopupID}{column.GetFilterProperty()}");
        }
        column.SetFilterValue(null);
        column.SetFilterValue(null, false);
        skip = 0;
        CurrentPage = 0;
        await InvokeAsync(Reload);
    }

    protected async Task ApplyFilter(RadzenGridColumn<TItem> column, bool closePopup = false)
    {
        if (closePopup)
        {
            await JSRuntime.InvokeVoidAsync("Radzen.closePopup", $"{PopupID}{column.GetFilterProperty()}");
        }
        OnFilter(new ChangeEventArgs() { Value = column.FilterValue }, column, true);
    }

    protected void ApplyDateFilterByFilterOperator(RadzenGridColumn<TItem> column, string filterOperator)
    {
        column.SetFilterOperator(filterOperator);
    }

    [Parameter]
    public DataGridSelectionMode SelectionMode { get; set; } = DataGridSelectionMode.Single;

    internal async Task OnRowClick(object item)
    {
        await RowClick.InvokeAsync((TItem)item);
        await OnRowSelect(item);
    }

    internal async System.Threading.Tasks.Task OnRowSelect(object item, bool raiseChange = true)
    {
        if (SelectionMode == DataGridSelectionMode.Single && item != null && selectedItems.Keys.Contains((TItem)item))
        {
            // Legacy RowSelect raise
            if (raiseChange)
            {
                await RowSelect.InvokeAsync((TItem)item);
            }
            return;
        }

        if (SelectionMode == DataGridSelectionMode.Single && selectedItems.Keys.Any())
        {
            var itemToDeselect = selectedItems.Keys.FirstOrDefault();
            if (itemToDeselect != null)
            {
                selectedItems.Remove(itemToDeselect);
                await RowDeselect.InvokeAsync(itemToDeselect);
            }
        }

        if (item != null)
        {
            if (!selectedItems.Keys.Contains((TItem)item))
            {
                selectedItems.Add((TItem)item, true);
                if (raiseChange)
                {
                    await RowSelect.InvokeAsync((TItem)item);
                }
            }
            else
            {
                selectedItems.Remove((TItem)item);
                await RowDeselect.InvokeAsync((TItem)item);
            }
        }
        else
        {
            if (raiseChange)
            {
                await RowSelect.InvokeAsync((TItem)item);
            }
        }

        var value = selectedItems.Keys;

        _value = SelectionMode == DataGridSelectionMode.Multiple ? (object)value : value.FirstOrDefault();
        ValueChanged?.Invoke(_value);
        StateHasChanged();
    }

    public async System.Threading.Tasks.Task SelectRow(TItem item)
    {
        await OnRowSelect(item, true);
    }

    internal async System.Threading.Tasks.Task OnRowDblClick(TItem item)
    {
        await RowDoubleClick.InvokeAsync(item);
    }

    [Parameter]
    public EventCallback<TItem> RowEdit { get; set; }

    [Parameter]
    public EventCallback<TItem> RowUpdate { get; set; }

    [Parameter]
    public EventCallback<TItem> RowCreate { get; set; }

    Dictionary<TItem, bool> editedItems = new Dictionary<TItem, bool>();
    Dictionary<TItem, EditContext> editContexts = new Dictionary<TItem, EditContext>();

    public async System.Threading.Tasks.Task EditRow(TItem item)
    {
        if(itemToInsert != null)
        {
            CancelEditRow(itemToInsert);
        }

        await EditRowInternal(item);
    }

    async System.Threading.Tasks.Task EditRowInternal(TItem item)
    {
        if (EditMode == DataGridEditMode.Single && editedItems.Keys.Any())
        {
            var itemToCancel = editedItems.Keys.FirstOrDefault();
            if (itemToCancel != null)
            {
                editedItems.Remove(itemToCancel);
                editContexts.Remove(itemToCancel);
            }
        }

        if (!editedItems.Keys.Contains(item))
        {
            editedItems.Add(item, true);

            var editContext = new EditContext(item);
            editContexts.Add(item, editContext);

            await RowEdit.InvokeAsync(item);

            StateHasChanged();
        }
    }

    public async System.Threading.Tasks.Task UpdateRow(TItem item)
    {
        if (editedItems.Keys.Contains(item))
        {
            var editContext = editContexts[item];

            if (editContext.Validate())
            {
                editedItems.Remove(item);
                editContexts.Remove(item);

                if (object.Equals(itemToInsert, item))
                {
                    await RowCreate.InvokeAsync(item);
                    itemToInsert = default(TItem);
                }
                else
                {
                    await RowUpdate.InvokeAsync(item);
                }
            }

            StateHasChanged();
        }
    }

    public void CancelEditRow(TItem item)
    {
        if (object.Equals(itemToInsert, item))
        {
            var list = this.PagedView.ToList();
            list.Remove(item);
            this._view = list.AsQueryable();
            this.Count = this.View.Count();
            itemToInsert = default(TItem);
            StateHasChanged();
        }
        else
        {
            int hash = item.GetHashCode();

            if (editedItems.Keys.Contains(item))
            {
                editedItems.Remove(item);
                editContexts.Remove(item);

                StateHasChanged();
            }
        }
    }

    public bool IsRowInEditMode(TItem item)
    {
        return editedItems.Keys.Contains(item);
    }

    TItem itemToInsert;
    public async System.Threading.Tasks.Task InsertRow(TItem item)
    {
        itemToInsert = item;
        var list = this.PagedView.ToList();
        list.Insert(0, item);
        this._view = list.AsQueryable();
        this.Count = this._view.Count();
        await EditRowInternal(item);
    }

    protected void OnSort(EventArgs args, RadzenGridColumn<TItem> column)
    {
        string property = column.GetSortProperty();
        if (AllowSorting && column.Sortable && !string.IsNullOrEmpty(property))
        {
            OrderBy(property);
        }
    }

    protected void OnFilterKeyPress(EventArgs args, RadzenGridColumn<TItem> column)
    {
        Debounce(() => DebounceFilter(column), FilterDelay);
    }

    async Task DebounceFilter(RadzenGridColumn<TItem> column)
    {
        var inputValue = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", getFilterInputId(column));
        if (!object.Equals(column.FilterValue, inputValue))
        {
            await InvokeAsync(() => { OnFilter(new ChangeEventArgs() { Value = inputValue }, column); });
        }
    }

    protected void OnFilter(ChangeEventArgs args, RadzenGridColumn<TItem> column, bool force = false, bool isFirst = true)
    {
        string property = column.GetFilterProperty();
        if (AllowFiltering && column.Filterable && !string.IsNullOrEmpty(property))
        {
            if (!object.Equals(isFirst ? column.FilterValue : column.SecondFilterValue, args.Value) || force)
            {
                column.SetFilterValue(args.Value, isFirst);
                skip = 0;
                CurrentPage = 0;
                InvokeAsync(Reload);
            }
        }
    }

    string orderBy;

    List<RadzenGridColumn<TItem>> columns = new List<RadzenGridColumn<TItem>>();

    public void AddColumn(RadzenGridColumn<TItem> column)
    {
        if (columns.IndexOf(column) == -1)
        {
            columns.Add(column);
            StateHasChanged();
        }
    }

    public void RemoveColumn(RadzenGridColumn<TItem> column)
    {
        if (columns.Contains(column))
        {
            columns.Remove(column);
            if (!disposed)
            {
                try { InvokeAsync(StateHasChanged); } catch { }
            }
        }
    }

    private bool IsOData()
    {
        return Data != null && typeof(ODataEnumerable<TItem>).IsAssignableFrom(Data.GetType());
    }

    public void OrderBy(string property)
    {
        SetColumnSortOrder(property);
        var p = IsOData() ? property.Replace('.', '/') : PropertyAccess.GetProperty(property);
        orderBy = orderBy == $"{p} desc" || orderBy == null || orderBy.IndexOf(p) == -1 ? $"{p} asc" : $"{p} desc";
        InvokeAsync(Reload);
    }

    public void OrderByDescending(string property)
    {
        SetColumnSortOrder(property);
        var p = IsOData() ? property.Replace('.', '/') : PropertyAccess.GetProperty(property);
        orderBy = $"{p} desc";
        InvokeAsync(Reload);
    }

    internal void SetColumnSortOrder(string property)
    {
        var column = columns.Where(c => c.GetSortProperty() == property).FirstOrDefault();
        if(column != null)
        {
            if (column.SortOrder == null)
            {
                column.SortOrder = SortOrder.Ascending;
            }
            else if (column.SortOrder == SortOrder.Ascending)
            {
                column.SortOrder = SortOrder.Descending;
            }
            else if (column.SortOrder == SortOrder.Descending)
            {
                column.SortOrder = null;
            }
        }
    }

    protected override string GetComponentCssClass()
    {
        var additionalClasses = new List<string>();

        if (CurrentStyle.ContainsKey("height"))
        {
            additionalClasses.Add("rz-has-height");
        }

        if (RowSelect.HasDelegate || ValueChanged != null || SelectionMode == DataGridSelectionMode.Multiple)
        {
            additionalClasses.Add("rz-selectable");
        }

        if (Responsive)
        {
            additionalClasses.Add("rz-datatable-reflow");
        }

        return $"rz-has-paginator rz-datatable  rz-datatable-scrollable {String.Join(" ", additionalClasses)}";
    }

    private string getHeaderStyle()
    {
        var additionalStyle = Style != null && Style.IndexOf("height:") != -1 ? "padding-right: 17px;" : "";
        return $"margin-left:0px;{additionalStyle}";
    }

    private string getFilterIconCss(RadzenGridColumn<TItem> column)
    {
        var additionalStyle = column.FilterValue != null || column.SecondFilterValue != null ? "rz-grid-filter-active" : "";
        return $"rzi rz-grid-filter-icon {additionalStyle}";
    }

    public Query Query { get; private set; } = new Query();

    private string PopupID
    {
        get
        {
            return $"popup{UniqueID}";
        }
    }

    public override void Dispose()
    {
        base.Dispose();

        foreach (var column in columns.Where(c => c.Visible))
        {
            JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", $"{PopupID}{column.GetFilterProperty()}");
        }
    }
}
